# -*- coding: utf-8 -*-
#
# Copyright (c) 2012, Rafael Pivato
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of Rafael Pivato nor the names of its contributors
#       may be used to endorse or promote products derived from this
#       software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL RAFAEL PIVATO BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import unicodedata

from datetime import datetime
from google.appengine.ext import db


class WikiPageModifiedError(Exception):
    """Indicates a Wiki Page optimistic editing lock failure."""

    def __init__(self, page_name):
        self.page_name = page_name
        self.message = 'The Wiki Page %s got modified while editing.'
        self.message = self.message % (page_name)


class WikiPage(db.Model):
    """Represents a Wiki Page."""

    title = db.StringProperty(required=True)
    author = db.StringProperty(default='')
    modified = db.DateTimeProperty(auto_now_add=True)
    view_role = db.StringProperty(choices=['admin', 'officer', 'member',
                                           'public'], default='public')
    edit_role = db.StringProperty(choices=['admin', 'officer', 'member'],
                                  default='member')
    content = db.TextProperty(default='')
    revision = db.IntegerProperty(default=0)

    def name(self):
        """Returns the page name for this object or None."""
        return self.key().id_or_name()

    def directives(self):
        """Return the directives given by current content."""
        return self.read_directives(self.content)

    @classmethod
    def read_directives(cls, content):
        inst = {}
        i, j = 0, 0
        while j < len(content):
            if content[j] == '\n':
                if content[j - 1:j] == '\r':
                    line = content[i:j - 1]
                else:
                    line = content[i:j]
                if line[:1] == '!':
                    key, val = line[1:].split(':', 1)
                    inst[key.strip().upper()] = val.strip()
                else:
                    break
                i = j + 1
            j = j + 1
        return inst

    @classmethod
    def title_from_content(cls, content):
        """Returns the title from the content."""
        inst = cls.read_directives(content)
        return inst['TITLE']

    @classmethod
    def key_from_name(cls, page_name):
        """Returns the key for a page from its page name."""
        if not page_name:
            page_name = u'Home'
        if not isinstance(page_name, unicode):
            raise Exception()
        lower = page_name.lower()
        key = u''
        previous = 'none'
        for c in lower:
            if c.isalpha():
                for d in unicodedata.normalize('NFKD', c):
                    if not unicodedata.combining(d):
                        if previous == 'digit':
                            key += '-'
                        key += d
                        previous = 'alpha'
            elif c.isdigit():
                if previous == 'alpha':
                    key += '-'
                key += c
                previous = 'digit'
            elif previous != 'separator':
                key += '-'
                previous = 'separator'
        return key.strip('-')

    @classmethod
    def load_page(cls, page_name):
        key_name = cls.key_from_name(page_name)
        instance = WikiPage.get_by_key_name(key_name)
        if instance is None:
            content = '!TITLE: %s\n\n' % (page_name)
            instance = WikiPage(key_name=key_name, title=page_name,
                                content=content)
        return instance

    @classmethod
    def edit_page(cls, page_name, revision, author, view_role, edit_role,
                  content):
        """Updates or creates a Wiki Page."""
        key_name = WikiPage.key_from_name(page_name)

        def txn():
            instance = WikiPage.get_by_key_name(key_name)
            if instance:
                if instance.revision > revision:
                    raise WikiPageModifiedError(key_name)
                WikiPageRevision.save_revision(instance)
            else:
                instance = WikiPage(key_name=key_name, title=page_name)
            content_title = cls.title_from_content(content)
            content_key = cls.key_from_name(content_title)
            if content_key == key_name:
                instance.title = content_title
            instance.author = author
            instance.modified = datetime.utcnow()
            instance.view_role = view_role
            instance.edit_role = edit_role
            instance.content = content
            instance.revision += 1
            instance.put()
            return instance
        return db.run_in_transaction(txn)

    @classmethod
    def latest_modifications(cls):
        """Return a list with latest modified pages."""
        query = cls.all()
        query.order('-modified')
        return query.fetch(20)


class WikiPageRevision(db.Model):
    """Every Wiki Page needs a revision.

    Every WikiPage should contain WikiPageRevision's for every modification
    made on its content. The content

    """

    author = db.StringProperty()
    modified = db.DateTimeProperty()
    view_role = db.StringProperty(choices=['admin', 'officer', 'member',
                                           'public'])
    edit_role = db.StringProperty(choices=['admin', 'officer', 'member'])
    content = db.TextProperty()

    @classmethod
    def save_revision(cls, page):
        """Saves a revision for a wiki page."""
        key = db.Key.from_path('WikiPageRevision', page.revision,
                               parent=page.key())
        model = cls(key=key, author=page.author, modified=page.modified,
                    view_role=page.view_role, edit_role=page.edit_role,
                    content=page.content)
        model.put()

    @classmethod
    def load_history(cls, page_name):
        """Loads the history for a wiki page."""
        query = WikiPageRevision.all()
        page_key = db.Key.from_path('WikiPage', page_name)
        query.ancestor(page_key)
        query.order('-modified')
        return query.fetch(20)
